"""
思考行动智能体，React(Reason 推理 和 Action 行动)，实现思考-行动循环模式。
目前think\act为抽象方法，必须由子类实现。
"""

from abc import abstractmethod
from typing import Optional, List, Union

from langchain_core.messages import HumanMessage, SystemMessage, AIMessage
from langchain_core.tools import BaseTool
from langgraph.checkpoint.memory import MemorySaver
from langgraph.graph.state import CompiledStateGraph
from langchain.agents import create_agent
from pydantic import Field

from app.agent.base import BaseAgent
from app.logger import logger


class ReActAgent(BaseAgent):
    memory_saver: MemorySaver = Field(default_factory=MemorySaver, description="Memory saver check pointer.")
    tools: List[Union[BaseTool]] = {}
    agent: Optional[CompiledStateGraph] = Field(None, description="Current agent state")

    system_prompt: Optional[None, str] = None
    ai_message_duplicate_threshold: int = Field(default=2, description="Duplicate threshold for the AI messages")
    is_stuck: bool = Field(default=False, description="Whether the agent is stuck")

    def __init__(self, **args):
        super().__init__(**args)
        if self.agent is None:
            self.agent = create_agent(
                model=self.llm.client,
                tools=self.tools,
                system_prompt=self.system_prompt,
                checkpointer=self.memory_saver
            )

    def check_stuck(self, last_step_output_msgs: List[Union[SystemMessage, HumanMessage, AIMessage]]):
        """Check if the agent is stuck in a loop by detecting duplicate content"""
        if len(last_step_output_msgs) < 2:
            return False

        last_step_output_msg = last_step_output_msgs['messages'][-1]
        duplicate_count = sum(
            1
            for msg in reversed(last_step_output_msgs['messages'][:-1])
            if isinstance(msg, AIMessage) and msg.content == last_step_output_msg.content
        )

        self.is_stuck = duplicate_count >= self.duplicate_threshold
        if self.is_stuck:
            logger.warning(f"Agent detected stuck state.")

    @abstractmethod
    async def execute(self) -> list:
        """
        Think about the next action to take.
        """

    async def step(self) -> list:
        return await self.execute()
